#include<iostream>
#include<string.h>
using namespace std;
#define MAXSIZE 100

typedef struct // 哈夫曼数的结构体
{
	int weight; // 结点的权值
	int parent, lchild, rchild; // 结点的双亲 左孩子 右孩子
}HTNode, * HuffmanTree;

typedef char** HuffmanCode; // 动态分配数组存储哈夫曼编码表

void Select(HuffmanTree HT, int t, int &s1, int &s2)
{	// 在哈夫曼树HT中 查找HT[k](1<=k<=i-1)
	// 选择其中两个双亲域为0且权值最小的结点 将序号保存s1和s2中
	for (int i = 1; i <= t; i++) // 初始化s1与s2的值
	{
		if (HT[i].parent == 0) // 判断双亲域是否为0
		{
			s1 = s2 = i;
			break;
		}
	}
	int temp; // 临时存储较小值
	for (int i = 1; i <= t; i++) // s1始终小于s2
		if (HT[i].parent == 0) // 判断双亲域是否为0
		{
			if (s1 == s2) // 处理s1与s2相等的情况(初始条件)
				if (HT[i].weight < HT[s1].weight) // 将小值赋值给s1
					s1 = i;
				else
					s2 = i;
			else if (HT[i].weight < HT[s1].weight && HT[i].weight < HT[s2].weight)
			{// 当前权值比s1与s2的权值都小时 按照s1始终小于s2赋值
				s2 = s1;
				s1 = i;
			}
			else if (HT[i].weight > HT[s1].weight && HT[i].weight < HT[s2].weight)
				s2 = i; // 当前权值只小于s2的权值时 直接赋值给s2
		}
}

void HT_OutPut(HuffmanTree HT, int n) // 输出哈夫曼树的存储结构
{
	cout << ">> 该哈夫曼树的存储结构如下:" << endl;
	cout << "\t结点\t权重\t父亲\t左孩子\t右孩子" << endl;
	for (int i = 1; i < 2*n; i++)
		cout << "\t  "
		<< i
		<< "\t  "
		<< HT[i].weight
		<< "\t  "
		<< HT[i].parent
		<< "\t    "
		<< HT[i].lchild
		<< "\t    "
		<< HT[i].rchild
		<< endl;
}

void CreateHuffmanTree(HuffmanTree &HT, int n) // 构造哈夫曼树 HT
{
	/*********初始化哈夫曼树:初始化 + 输入权值*********/
	if (n <= 1)
		return;
	int m = 2 * n - 1; // m 为要储存的总结点数
	HT = new HTNode[m + 1]; // 申请 2n 个结点空间
	// 注意:HT[m]代表根结点
	for (int i = 0; i <= m; i++)
	// 将1~m号单元中双亲、左孩子和右孩子的下标都初始化为0
	{
		HT[i].weight = 0;
		HT[i].parent = 0;
		HT[i].lchild = 0;
		HT[i].rchild = 0;
	}
	cout << "依次输入叶子结点的权值: ";
	for (int i = 1; i <= n; i++) // 依次输入前n个单元中叶子结点的权值
		cin >> HT[i].weight;
	getchar();
	/********创建哈夫曼树:n-1次选择、删除、合并********/
	for (int i = n + 1; i <= m; i++)
	{
		int s1, s2; // 临时储存结点的序号
		Select(HT, i - 1, s1, s2); // 调用函数进行选择操作
		// 将s1、s2两个结点的双亲域修改为确定的序号i (删除操作)
		HT[s1].parent = i;
		HT[s2].parent = i;
		// 将s1、s2设置为i的孩子 (合并操作)
		HT[i].lchild = s1;
		HT[i].rchild = s2;
		// 确定序号为 i 的结点的权值
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
}

void CreatHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n)
{
	HC = new char* [n + 1]; // 分配存储n个字符编码的编码表空间
	char* cd = new char[n]; // 临时存储每个字符的编码的动态数组空间
	cd[n - 1] = '\0'; // 编码结束符
	for (int i = 1; i <= n; i++) // 逐个字符求哈夫曼编码
	{
		int start = n - 1; // start开始时指向编码结束位置
		int c = i;
		int f = HT[i].parent; // f指向结点c的双亲结点
		while (f != 0) // 从叶子结点向上回溯 直到根结点
		{
			start--; // 回溯一次start向前指一个位置
			if (HT[f].lchild == c) // 结点c是f的左孩子
				cd[start] = '0'; // 生成代码0
			else // 结点c是f的右孩子
				cd[start] = '1'; // 生成代码1
			c = f;
			f = HT[f].parent; // 向上回溯
		}
		HC[i] = new char[n - start]; // 为第i个字符编码分配适当大小的空间
		strcpy(HC[i], &cd[start]); // 将所求得的编码从cd空间复制到HC的当前行中
	}
	delete cd; // 释放临时空间
}

void HC_OutPut(HuffmanCode HC, int n) // 输出哈夫曼编码表及平均查找长度
{
	int length = 0;
	cout << ">> 下面是哈夫曼编码表: " << endl;
	for (int i = 1; i <= n; i++) // 遍历编码表的每一行
	{
		int j = 0;
		cout << "\t编码" << i << ": ";
		while (HC[i][j] != '\0') // 对编码表的某行进行遍历输出
		{
			cout << HC[i][j++];
			length++;
		}
		cout << endl;
	}
	cout << "\t>> 平均查找长度为: " << (double)length / n << endl;
}

void TransHuffmanCode(HuffmanTree HT,int n, char* cd) // 译码操作
{
	int start; // 存储根结点的序号
	for (int i = 1; i <= 2 * n - 1; i++) // 寻找根结点 并给start赋值
		if (HT[i].parent == 0)
		{
			start = i;
			break;
		}
	int p = start; // 定义临时变量p 存储当前的遍历序号
	cout << "译码后的结果: ";
	while (*cd != '\0') // 遍历所有编码
	{
		if (HT[p].lchild == 0 && HT[p].rchild == 0) // 判断是否是叶子结点
		{
			cout << HT[p].weight << " "; // 如果是叶子结点 则输出
			p = start; // 同时 当前序号复位为根结点序号
		}
		if (*cd == '0') // 判断遍历方向(是否遍历左孩子)
			p = HT[p].lchild;
		else if (*cd == '1') // 判断遍历方向(是否遍历右孩子)
			p = HT[p].rchild;
		cd = cd + 1; // 遍历下一个编码
	}
	cout << endl;
}

int main()
{
	HuffmanTree HT; // 定义一个哈夫曼树
	HuffmanCode HC; // 定义一个编码表
	char cd[MAXSIZE], ch;
	int n; // 存储叶子结点个数
	cout << "请输入您生成的哈夫曼树的叶子结点个数: ";
	cin >> n;
	CreateHuffmanTree(HT, n); // 生成哈夫曼树 
	CreatHuffmanCode(HT, HC, n); // 生成哈夫曼编码表
	HT_OutPut(HT, n); // 输出哈夫曼树
	HC_OutPut(HC, n); // 输出哈夫曼编码表及平均查找长度
	cout << "请在上述哈夫曼编码中选择你要译码的编码: ";
	int i = 0;
	while ((ch = getchar()) != '\n') // 接收输入的编码
		cd[i++] = ch;
	cd[++i] = '\0'; // 编码结束的标志
	TransHuffmanCode(HT, n, cd); // 译码操作
	system("pause");
	return 0;
}
